#ifndef BASE_OBJECT_POOL_H
#define BASE_OBJECT_POOL_H

#include "Defind.hpp"
#include "TimeUnit.hpp"
#include "BaseLog.hpp"

#include <ctime>
#include <memory>
#include <atomic>
#include <mutex>
#include <queue>

namespace DBServer
{
namespace Base
{

template<class T>
class ObjectPool {
public:
    void back(std::shared_ptr<T>&& ptr) {
        if (is_stop.load())
        {   
            // if the object is stop, release source by itself
            return;
        }
        {
            std::unique_lock<std::mutex> lock(back_mtx);
            if (check() == EASY)
            {   // reduction
                reduction();
            } else {
                pool.push(ptr);
                ++free_object;
            }
        }
    }

    std::shared_ptr<T> get() {
        if (is_stop.load())
            return nullptr;
        std::shared_ptr<T> ob;
        {
            std::unique_lock<std::mutex> lock(get_mtx);
            // judge if need to dalitation
            if (check() == STORTAGE) dalitation(); 
            ob = std::move(pool.front());
            pool.pop();
            --free_object;
        }
        return ob;
    }

    void stop() {
        is_stop.store(true);
    }

    ObjectPool()
        : object_num(OBJECT_POOL_DEFINE_NUM), free_object(0)
    {
        // working
        is_stop.store(false);
        // init the pool
        init();
    }

    ~ObjectPool() {
        for (int i = 0; i < (int)free_object; i++)
        {
            auto ob = std::move(pool.front());
            pool.pop();
        }
    }

    ObjectPool(const ObjectPool&) = delete;
    ObjectPool(const ObjectPool&&) = delete;
    ObjectPool&& operator=(const ObjectPool&&) = delete;
    const ObjectPool& operator=(const ObjectPool&) = delete;
protected:
    int check()
    {
        if (get_use_situation() < OBJECT_EASY)
            return EASY;
        else if (get_use_situation() < OBJECT_STORAGE) 
            return MODERATION; 
        return STORTAGE;
    }

    double get_use_situation() {
        return (double)(object_num - free_object) / (double)object_num;
    }

    void init() {
        for (int i = 0; i < object_num; i++)
        {
            // maybe the memory is not enough 
            try
            {
                // pakage the object into the smart pointer
                std::unique_ptr<T> object(new T);
                pool.push(std::move(object));       
            }
            catch(const std::exception& e)
            {
                // if error stop new and continue to work
                error(const_cast<char*>(e.what()));
                object_num = pool.size();
                free_object = pool.size();
                break;
            }
        }
    }
    
    void dalitation()          // call back function, when call get()
    {
        // log the usage situation
        char format[] = "ObjectPool is dalitating!\n";
        info(format);

        // culcalate hom many object will need
        double num = (((double)(object_num-free_object))/OBJECT_MODERATION) - free_object;
        // counter
        Timer::Time clock;
        clock.cur();
        time_t begin_time = clock.get_time();
        // begin dalitate
        {
            std::unique_lock<std::mutex> lock(back_mtx);
            for (int i = 0; i < num; i++)
            {
                std::shared_ptr<T> object(new T);
                pool.push(std::move(object));
                ++object_num;
                ++free_object;
            }
        }
        clock.cur();
        char log_msg[] = "ObjectPool dalitation cost %u\nobject_num = %d\nfree_num = %d\n";
        info(log_msg, begin_time - clock.get_time(), object_num, free_object);
    }

    void reduction()           // call back function, when call back()
    {
        // log
        char format[] = "ObjectPool is reducting\n";
        info(format);

        // calculate
        double num = free_object - (object_num - (((double)(object_num-free_object))/OBJECT_MODERATION));
        // counter
        Timer::Time clock;
        clock.cur();
        // begin
        time_t begin_time = clock.get_time();
        {
            std::unique_lock<std::mutex> lock(get_mtx);
            for (int i = 0; i < num; i++) 
            {
                auto ob = std::move(pool.front());
                pool.pop();
                --object_num;
                --free_object;
            }
        }
        clock.cur();

        char log_msg[] = "ObjectPool reduction cost %u\nObject num = %d\nfree_num = %d\n";
        info(log_msg, begin_time - clock.get_time(), object_num, free_object);
    }

private:
    std::atomic_int                         is_stop;
    unsigned long long                      object_num;
    std::queue<std::shared_ptr<T>>          pool;
    unsigned long long                      free_object;
    std::mutex                              get_mtx;
    std::mutex                              back_mtx;

};
} // namespace Socket    
} // namespace DBServer

#endif